#include "StdAfx.h"
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include "TimeStampRequest.h"
#include "GemsecUtilities.h"
#include "SafeExceptions.h"
#include "errors.h"

// Link with the Crypt32.lib file.
//#pragma comment (lib, "Crypt32")
namespace Safe {
	
	TimestampRequest::~TimestampRequest(void)
	{
	}

	TimestampRequest::TimestampRequest(ManagedCryptBlob^ hash, System::String^ hashAlg ) {
		this->m_hash = hash;
		int rc = CreateRequest( 1, hashAlg, true );
		if( rc != 0 ) 
		{
			throw gcnew Safe::AsnException(rc);
		}
	}
	
	int TimestampRequest::CreateRequest(int version, System::String^ hashOid, bool reqServerCert) {
		// convert the OID
		System::IntPtr ptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(hashOid);
		const char *ansiOid = static_cast<const char*>(ptr.ToPointer());
		int rc = CreateRequest( version, ansiOid, reqServerCert );
		System::Runtime::InteropServices::Marshal::FreeHGlobal(ptr);
		return rc;
	}



	// Creates the outer sequence of the timestamp request
	int TimestampRequest::CreateRequest(int version, const char* hashOid, bool reqServerCert)
	{
		CRYPT_SEQUENCE_OF_ANY outerSeq;
		outerSeq.cValue = 3;
		outerSeq.rgValue = new CRYPT_DER_BLOB[outerSeq.cValue];
		memset( outerSeq.rgValue, 0, sizeof(CRYPT_DER_BLOB) * 3 );
		/// Encode the version number
		if (0 != CGemsecUtilities::Encode(&version, X509_INTEGER, outerSeq.rgValue[0]))
		{
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[0]);
			delete [] outerSeq.rgValue;
			return ENCODE_VER_FAIL;
		}
		
		/// Create the inner request sequence
		int retval = CreateRequestInnerSeq(hashOid, outerSeq.rgValue[1]);
		if (0 != retval) { 
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[0]);
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[1]);
			delete [] outerSeq.rgValue;
			return ENCODE_ALGID_FAIL;
		}

		/// Encode the flag to tell the server whether or not to return the
		/// timestamp authority's certificate
		retval = EncodeBoolean(reqServerCert, outerSeq.rgValue[2]);
		if (0 != retval) { 
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[0]);
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[1]);
			CGemsecUtilities::FreeMem(&outerSeq.rgValue[2]);
			delete [] outerSeq.rgValue;
			return ENCODE_FAIL;
		}

		CRYPT_DER_BLOB blob;
		blob.pbData = NULL;
		blob.cbData = 0;
		/// Create the DER sequence from the three inner elements
		int rc = CGemsecUtilities::Encode(&outerSeq, X509_SEQUENCE_OF_ANY, blob);
		if( rc == 0 && blob.cbData > 0 ) { 
			/// copy the outer sequence into the m_request member variable
			this->m_request = gcnew ManagedCryptBlob(blob);
		}
		/// delete the allocated memory
		CGemsecUtilities::FreeMem( &blob );
		CGemsecUtilities::FreeMem(&outerSeq.rgValue[0]);
		CGemsecUtilities::FreeMem(&outerSeq.rgValue[1]);
		CGemsecUtilities::FreeMem(&outerSeq.rgValue[2]);
		delete [] outerSeq.rgValue;
		return rc != 0 ? ENCODE_OUTERSEQ_FAIL : 0;
	}



	// creates inner sequence of timestamp request
	int TimestampRequest::CreateRequestInnerSeq(const char *hashOid, CRYPT_DER_BLOB &encoded)
	{
		int rc = 0;
		CRYPT_SEQUENCE_OF_ANY algAndHash;
		algAndHash.cValue = 2;
		algAndHash.rgValue = new CRYPT_DER_BLOB[algAndHash.cValue];
		memset( algAndHash.rgValue, 0, sizeof( CRYPT_DER_BLOB ) * algAndHash.cValue );

		CRYPT_ALGORITHM_IDENTIFIER algid;
		algid.Parameters.cbData = 0;
		algid.Parameters.pbData = NULL;
		algid.pszObjId = (LPSTR)hashOid;

		/// encode the hash algorithm identifier
		rc = CGemsecUtilities::Encode(&algid, X509_ALGORITHM_IDENTIFIER, algAndHash.rgValue[0]);
		
		if( rc != 0 ) 
		{
	#ifndef NOTREALLYVISTA
			rc = ENCODE_ALGID_FAIL;
	#else
			static BYTE fakealgid[] = {0x30, 0x09, 0x06, 0x05, 0x2B, 
									   0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00};
			CGemsecUtilities::CreateBlob(&algAndHash.rgValue[0], fakealgid, 11);
	#endif
		}
		else
		{
			CRYPT_DER_BLOB hashBlob;
			hashBlob.cbData = m_hash->Length;
			hashBlob.pbData = new BYTE[ hashBlob.cbData ];
			memset( hashBlob.pbData, 0, sizeof(BYTE)*hashBlob.cbData);
			if( !m_hash->CreateBlob( hashBlob ) ) { 
				rc = ENCODE_HASH_FAIL;
			} else { 
				/// encode the hash
				if( 0 != CGemsecUtilities::Encode(&hashBlob, X509_OCTET_STRING, algAndHash.rgValue[1]) ) { 
					rc = ENCODE_HASH_FAIL;
				} else if (0 != CGemsecUtilities::Encode(&algAndHash, X509_SEQUENCE_OF_ANY, encoded)) {
					rc = ENCODE_HASH_FAIL;
				}
			}
			delete [] hashBlob.pbData;
			hashBlob.pbData = NULL;
		}

		CGemsecUtilities::FreeMem(&algAndHash.rgValue[0]);
		CGemsecUtilities::FreeMem(&algAndHash.rgValue[1]);		
		delete [] algAndHash.rgValue;
		return rc;
	}



	// returns a DER encoded boolean value
	int TimestampRequest::EncodeBoolean(bool value, CRYPT_DER_BLOB &encoded)
	{
		static BYTE encT[] = {0x01, 0x01, 0xFF};
		static BYTE encF[] = {0x01, 0x01, 0x00};

		encoded.cbData = 3;
		encoded.pbData = new BYTE[3];
		memcpy_s(encoded.pbData, encoded.cbData, (value ? encT : encF), encoded.cbData);
		
		return 0;
	}
}